using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;

namespace EPPlayer
{
    /// <summary>
    /// The main event for the application layer, EPCharacter provides all the needed
    /// functionality to build and operate an EP character. Our main task is to supply
    /// the Model with all the known game rules.
    /// </summary>

    public class EPCharacter : Entity, INotifyPropertyChanged
    {
        /// <note>
        /// This has to be maintained manually, as there's no built-in way to 
        /// automatically maintain a subset of another ObserverableCollection
        /// </note>
        internal ObservableCollection<Attribute> NonValueAttributes = new ObservableCollection<Attribute>();

        // Overriding the Collection<T> default behaviour
        protected override void InsertItem(int index, Attribute newItem)
        {
            Type t = newItem.GetType();
            if (    t == typeof(Morph) ||
                    t == typeof(Background) ||
                    t == typeof(Faction) ||
                    t == typeof(Trait) ||
                    t == typeof(Gear) )
            {
                NonValueAttributes.Add(newItem);
            }
            base.InsertItem(index, newItem);
        }

        protected override void RemoveItem(int index)
        {
            Attribute removedItem = Items[index];
            NonValueAttributes.Remove(removedItem);
            base.RemoveItem(index);
        }

        public void DeprecatedAttachAttribute(string Color, string Name)
        {
            AttachableAttribute NewAttribute = null;
            Boolean isExclusive = true;
            switch (Color)
            {
                case Faction.AttributeColor:
                    NewAttribute = GameResources.Factions[Name];
                    break;
                case Background.AttributeColor:
                    NewAttribute = GameResources.Backgrounds[Name];
                    break;
                case Morph.AttributeColor:
                    NewAttribute = GameResources.Morphs[Name];
                    break;
                case Trait.AttributeColor:
                    NewAttribute = GameResources.Traits[Name];
                    isExclusive = false;
                    break;
                case Gear.AttributeColor:
                    NewAttribute = GameResources.Gear[Name];
                    isExclusive = false;
                    break;
            }
            if (isExclusive == true)
            {
                while (true)
                {
                    Attribute Att = this.FirstOrDefault(El => El.GetType() == NewAttribute.GetType());
                    if (Att != null)
                    {
                        this.Remove(Att);
                    }
                    else
                    {
                        break;
                    }
                }
            }
            if (NewAttribute != null)
            {
                this.Add(NewAttribute);
            }
            else
            {
                throw new ArgumentException("Attribute not found");
            }
            NotifyPropertyChanged("BilledCPCost");
        }

        public void DeprecatedDetachAttribute(string Type, string Name)
        {
            Attribute Att = this.FirstOrDefault(El => El.name == Name && El.color == Type);
            if (Att != null)
            {
                this.Remove(Att);
            }
            NotifyPropertyChanged("BilledCPCost");
        }

        public IEnumerable<Skill> Skills
        {
            get
            {
                IEnumerable<Skill> Set = this.OfType<Skill>();
                return Set;
            }
        }
        public IEnumerable<Skill> KnowledgeSkills
        {
            get
            {
                return this.Skills.Where(Sk => Sk.category == "Knowledge");
            }
        }
        public IEnumerable<Skill> ActiveSkills
        {
            get
            {
                return this.Skills.Where(Sk => Sk.category == "Active");
            }
        }
        public IEnumerable<Skill> CombatSkills
        {
            get
            {
                return this.Skills.Where(Sk => Sk.group == "Combat");
            }
        }
        public IEnumerable<Skill> ActionSkills
        {
            get
            {
                return this.Skills.Where(Sk => Sk.group == "Action");
            }
        }
        public IEnumerable<Skill> MovementSkills
        {
            get
            {
                return this.Skills.Where(Sk => Sk.group == "Movement");
            }
        }
        public IEnumerable<Skill> InterpersonalSkills
        {
            get
            {
                return this.Skills.Where(Sk => Sk.group == "Interpersonal");
            }
        }

        public Morph Morph
        {
            get
            {
                return this.OfType<Morph>().FirstOrDefault();
            }
        }

        /*
        Spend Free Points (p. 134)
            105 aptitude points (ok)
            1 Moxie             (ok)
            5,000 credit        (ok)
            50 Rep              (ok)
            Native tongue       (todo)
        Spend Customization Points (p. 135)
            1,000 CP to spend
            15 CP = 1 Moxie                  (ok)
            10 CP = 1 aptitude point         (ok)
            5 CP = 1 psi sleight             (todo)
            5 CP = 1 specialization          (todo)
            2 CP = 1 skill point (61-80)     (ok)
            1 CP = 1 skill point (up to 60)  (ok)
            1 CP = 1,000 credit              (ok)
            1 CP = 10 rep                    (ok)
        */

        public int PointsInAptitudes
        {
            get
            {
                return this.OfType<Aptitude>().Select(Va => Va.rawValue).Sum();
            }
        }

        private static int SkillPointCost(ValueAttribute Va)
        {
            // todo: Any skill or aptitude bonuses from gear are treated as modifications; 
            // they are applied after all CP are spent and do not affect the cost of buying skills or aptitudes during character creation.
            int Above60 = Math.Max(Va.cookedValue - 60, 0);
            int Below60 = Va.rawValue - Above60;
            return Below60 + (Above60 * 2);
        }

        public int PointsInKnowledgeSkills
        {
            get
            {
                IEnumerable<ValueAttribute> Skills = this.KnowledgeSkills;
                return Skills.Sum(Sk => SkillPointCost(Sk));
            }
        }

        public int PointsInActiveSkills
        {
            get
            {
                IEnumerable<ValueAttribute> Skills = this.ActiveSkills;
                return Skills.Sum(Sk => SkillPointCost(Sk));
            }
        }

        public int PointsInRep
        {
            get
            {
                return this.OfType<Reputation>().Select(Va => Va.rawValue).Sum();
            }
        }

        /// <summary>
        /// CP cost minus freebies
        /// </summary>
        public int BilledCPCost
        {
            get
            {
                int Sum, Cost = 0;

                Sum = this.PointsInRep;
                if (Sum > 50)
                {
                    Cost += (Sum - 50) / 10;
                }
                Sum = this.PointsInAptitudes;
                if (Sum > 105)
                {
                    Cost += 10 * (Sum - 105);
                }
                Cost += this.PointsInActiveSkills + this.PointsInKnowledgeSkills;

                Sum = this.GetCookedValue("Credits");
                Sum += this.CreditCost;
                if (Sum > 5000)
                {
                    Cost += (Sum - 5000) / 1000;
                }

                Sum = GetRawValue("Moxie");
                if (Sum > 1)
                {
                    Cost += (Sum - 1) * 15;
                }

                Cost += this.CommonCost;

                return Cost;
            }
        }

        public int CPCost
        {
            get
            {
                int Cost = 0;

                Cost += this.PointsInAptitudes * 10;
                Cost += this.PointsInRep / 10;
                Cost += this.PointsInActiveSkills + this.PointsInKnowledgeSkills;

                Cost += GetRawValue("Credits") / 1000;
                Cost += GetRawValue("Moxie") * 15;
                Cost += this.CommonCost;

                return Cost;
            }
        }

        private int CommonCost
        {
            get
            {
                int Cost = 0;

                // morph CP cost
                Morph M = this.Morph;
                if (M != null)
                {
                    Cost += M.CPCost;
                }

                Cost += this.OfType<Trait>().Select(Aa => Aa.cpCost).Sum();

                return Cost;
            }
        }

        /// <summary>
        /// translate cost classes (like "Moderate") using the average credit cost table
        /// </summary>
        public int CreditCost
        {
            get
            {
                IEnumerable<Gear> GearList = this.OfType<Gear>();
                return GearList.Select(Ge => GameResources.CostAverage[Ge.creditCost]).Sum();
            }
        }

        // This constructor creates a brand new character
        
        public EPCharacter()
            : base()
        {
            this.CollectionChanged += AAttributesChanged;

            foreach (Aptitude Apt in GameResources.Aptitudes.Values)
            {
                Aptitude Clone = new Aptitude(Apt.name, Apt.shortName, Apt.description);
                this.Add(Clone);
                Clone.PropertyChanged += VAttributeChanged;
            }
            foreach (Stat Stat in GameResources.Stats.Values)
            {
                Stat Clone = new Stat(Stat.name, Stat.description);
                this.Add(Clone);
                Clone.PropertyChanged += VAttributeChanged;
            }
            foreach (Skill s in GameResources.Skills.Values)
            {
                Skill Clone = new Skill(s.name, s.governingAptitude, s.allowsDefaulting, s.category, s.group, s.description);
                this.Add(Clone);
                Clone.PropertyChanged += VAttributeChanged;
            }
            foreach (Reputation Rep in GameResources.Reputations.Values)
            {
                Reputation Clone = new Reputation(Rep.name, Rep.description);
                this.Add(Clone);
                Clone.PropertyChanged += VAttributeChanged;
            }

            // Improvement: The relationships are being expressed twice here,
            // once in the Lambda and once in the AddDependent call.

            AttributeFilter Af = new AttributeFilter("Initiative", "CoreRules", null,
                this["Initiative"], a => a + (this.GetCookedValue("Intuition") + this.GetCookedValue("Reflexes") * 2) / 5);
            this.Add(Af);
            (this["Intuition"] as ValueAttribute).AddDependent(this["Initiative"]);
            (this["Reflexes"] as ValueAttribute).AddDependent(this["Initiative"]);

            Af = new AttributeFilter("Lucidity", "CoreRules", null,
                this["Lucidity"], a => a + this.GetCookedValue("Willpower") * 2);
            this.Add(Af);
            (this["Willpower"] as ValueAttribute).AddDependent(this["Lucidity"]);

            Af = new AttributeFilter("Trauma Threshold", "CoreRules", null,
                this["Trauma Threshold"], a => a + this.GetCookedValue("Lucidity") / 5);
            this.Add(Af);
            (this["Lucidity"] as ValueAttribute).AddDependent(this["Trauma Threshold"]);

            Af = new AttributeFilter("Insanity Rating", "CoreRules", null,
                this["Insanity Rating"], a => a +this.GetCookedValue("Lucidity") * 2);
            this.Add(Af);
            (this["Insanity Rating"] as ValueAttribute).AddDependent(this["Lucidity"]);

            Af = new AttributeFilter("Damage Bonus", "CoreRules", null,
                this["Damage Bonus"],  a => a + this.GetCookedValue("Somatics") / 10);
            this.Add(Af);
            (this["Somatics"] as ValueAttribute).AddDependent(this["Damage Bonus"]);

            this.SetRawValue("Moxie", 1);
            this.SetRawValue("Speed", 1);
            this.SetRawValue("CP", 1000);
            this.SetRawValue("Credits", 5000);
        }

        public new event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public void AAttributesChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            var NeedsUpdate = false;
            if (e.NewItems != null)
            {
                foreach (object o in e.NewItems)
                {
                    if (!(o.GetType() == typeof(Faction) ||
                          o.GetType() == typeof(Background)))
                    {
                        NeedsUpdate = true;
                    }
                }
            }
            if (NeedsUpdate == false && e.OldItems != null)
            {
                foreach (object o in e.OldItems)
                {
                    if (!(o.GetType() == typeof(Faction) ||
                          o.GetType() == typeof(Background)))
                    {
                        NeedsUpdate = true;
                    }
                }
            }
            if (NeedsUpdate == true)
            {
                NotifyPropertyChanged("BilledCPCost");
            }
        }
        public void VAttributeChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e == null)
            {
                Debug.WriteLine("Skip");
            }
            else
            {
                NotifyPropertyChanged("BilledCPCost");
                NotifyPropertyChanged("PointsInActiveSkills");
                NotifyPropertyChanged("PointsInKnowledgeSkills");
            }
        }
    }
}